<audio title="35 _ join语句怎么优化？" src="https://static001.geekbang.org/resource/audio/f1/64/f11daf984db1e1ddc708f5b286c8e064.mp3" controls="controls"></audio> 
<p>在上一篇文章中，我和你介绍了join语句的两种算法，分别是Index Nested-Loop Join(NLJ)和Block Nested-Loop Join(BNL)。</p><p>我们发现在使用NLJ算法的时候，其实效果还是不错的，比通过应用层拆分成多个语句然后再拼接查询结果更方便，而且性能也不会差。</p><p>但是，BNL算法在大表join的时候性能就差多了，比较次数等于两个表参与join的行数的乘积，很消耗CPU资源。</p><p>当然了，这两个算法都还有继续优化的空间，我们今天就来聊聊这个话题。</p><p>为了便于分析，我还是创建两个表t1、t2来和你展开今天的问题。</p><pre><code>create table t1(id int primary key, a int, b int, index(a));
create table t2 like t1;
drop procedure idata;
delimiter ;;
create procedure idata()
begin
  declare i int;
  set i=1;
  while(i&lt;=1000)do
    insert into t1 values(i, 1001-i, i);
    set i=i+1;
  end while;
  
  set i=1;
  while(i&lt;=1000000)do
    insert into t2 values(i, i, i);
    set i=i+1;
  end while;

end;;
delimiter ;
call idata();
</code></pre><p>为了便于后面量化说明，我在表t1里，插入了1000行数据，每一行的a=1001-id的值。也就是说，表t1中字段a是逆序的。同时，我在表t2中插入了100万行数据。</p><h1>Multi-Range Read优化</h1><p>在介绍join语句的优化方案之前，我需要先和你介绍一个知识点，即：Multi-Range Read优化(MRR)。这个优化的主要目的是尽量使用顺序读盘。</p><p>在<a href="https://time.geekbang.org/column/article/69236">第4篇文章</a>中，我和你介绍InnoDB的索引结构时，提到了“回表”的概念。我们先来回顾一下这个概念。回表是指，InnoDB在普通索引a上查到主键id的值后，再根据一个个主键id的值到主键索引上去查整行数据的过程。</p><!-- [[[read_end]]] --><p>然后，有同学在留言区问到，回表过程是一行行地查数据，还是批量地查数据？</p><p>我们先来看看这个问题。假设，我执行这个语句：</p><pre><code>select * from t1 where a&gt;=1 and a&lt;=100;
</code></pre><p>主键索引是一棵B+树，在这棵树上，每次只能根据一个主键id查到一行数据。因此，回表肯定是一行行搜索主键索引的，基本流程如图1所示。</p><p><img src="https://static001.geekbang.org/resource/image/97/05/97ae269061192f6d7a632df56fa03605.png" alt=""></p><center><span class="reference">图1 基本回表流程</span></center><p>如果随着a的值递增顺序查询的话，id的值就变成随机的，那么就会出现随机访问，性能相对较差。虽然“按行查”这个机制不能改，但是调整查询的顺序，还是能够加速的。</p><p><strong>因为大多数的数据都是按照主键递增顺序插入得到的，所以我们可以认为，如果按照主键的递增顺序查询的话，对磁盘的读比较接近顺序读，能够提升读性能。</strong></p><p>这，就是MRR优化的设计思路。此时，语句的执行流程变成了这样：</p><ol>
<li>
<p>根据索引a，定位到满足条件的记录，将id值放入read_rnd_buffer中;</p>
</li>
<li>
<p>将read_rnd_buffer中的id进行递增排序；</p>
</li>
<li>
<p>排序后的id数组，依次到主键id索引中查记录，并作为结果返回。</p>
</li>
</ol><p>这里，read_rnd_buffer的大小是由read_rnd_buffer_size参数控制的。如果步骤1中，read_rnd_buffer放满了，就会先执行完步骤2和3，然后清空read_rnd_buffer。之后继续找索引a的下个记录，并继续循环。</p><p>另外需要说明的是，如果你想要稳定地使用MRR优化的话，需要设置<code>set optimizer_switch="mrr_cost_based=off"</code>。（官方文档的说法，是现在的优化器策略，判断消耗的时候，会更倾向于不使用MRR，把mrr_cost_based设置为off，就是固定使用MRR了。）</p><p>下面两幅图就是使用了MRR优化后的执行流程和explain结果。</p><p><img src="https://static001.geekbang.org/resource/image/d5/c7/d502fbaea7cac6f815c626b078da86c7.jpg" alt=""></p><center><span class="reference">图2 MRR执行流程</span></center><p><img src="https://static001.geekbang.org/resource/image/a5/32/a513d07ebaf1ae044d44391c89bc6432.png" alt=""></p><center><span class="reference">图3 MRR执行流程的explain结果</span></center><p>从图3的explain结果中，我们可以看到Extra字段多了Using MRR，表示的是用上了MRR优化。而且，由于我们在read_rnd_buffer中按照id做了排序，所以最后得到的结果集也是按照主键id递增顺序的，也就是与图1结果集中行的顺序相反。</p><p>到这里，我们小结一下。</p><p><strong>MRR能够提升性能的核心</strong>在于，这条查询语句在索引a上做的是一个范围查询（也就是说，这是一个多值查询），可以得到足够多的主键id。这样通过排序以后，再去主键索引查数据，才能体现出“顺序性”的优势。</p><h1>Batched Key Access</h1><p>理解了MRR性能提升的原理，我们就能理解MySQL在5.6版本后开始引入的Batched Key Access(BKA)算法了。这个BKA算法，其实就是对NLJ算法的优化。</p><p>我们再来看看上一篇文章中用到的NLJ算法的流程图：</p><p><img src="https://static001.geekbang.org/resource/image/10/3d/10e14e8b9691ac6337d457172b641a3d.jpg" alt=""></p><center><span class="reference">图4 Index Nested-Loop Join流程图</span></center><p>NLJ算法执行的逻辑是：从驱动表t1，一行行地取出a的值，再到被驱动表t2去做join。也就是说，对于表t2来说，每次都是匹配一个值。这时，MRR的优势就用不上了。</p><p>那怎么才能一次性地多传些值给表t2呢？方法就是，从表t1里一次性地多拿些行出来，一起传给表t2。</p><p>既然如此，我们就把表t1的数据取出来一部分，先放到一个临时内存。这个临时内存不是别人，就是join_buffer。</p><p>通过上一篇文章，我们知道join_buffer 在BNL算法里的作用，是暂存驱动表的数据。但是在NLJ算法里并没有用。那么，我们刚好就可以复用join_buffer到BKA算法中。</p><p>如图5所示，是上面的NLJ算法优化后的BKA算法的流程。</p><p><img src="https://static001.geekbang.org/resource/image/68/88/682370c5640244fa3474d26cc3bc0388.png" alt=""></p><center><span class="reference">图5 Batched Key Access流程</span></center><p>图中，我在join_buffer中放入的数据是P1~P100，表示的是只会取查询需要的字段。当然，如果join buffer放不下P1~P100的所有数据，就会把这100行数据分成多段执行上图的流程。</p><p>那么，这个BKA算法到底要怎么启用呢？</p><p>如果要使用BKA优化算法的话，你需要在执行SQL语句之前，先设置</p><pre><code>set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
</code></pre><p>其中，前两个参数的作用是要启用MRR。这么做的原因是，BKA算法的优化要依赖于MRR。</p><h1>BNL算法的性能问题</h1><p>说完了NLJ算法的优化，我们再来看BNL算法的优化。</p><p>我在上一篇文章末尾，给你留下的思考题是，使用Block Nested-Loop Join(BNL)算法时，可能会对被驱动表做多次扫描。如果这个被驱动表是一个大的冷数据表，除了会导致IO压力大以外，还会对系统有什么影响呢？</p><p>在<a href="https://time.geekbang.org/column/article/79407">第33篇文章</a>中，我们说到InnoDB的LRU算法的时候提到，由于InnoDB对Bufffer Pool的LRU算法做了优化，即：第一次从磁盘读入内存的数据页，会先放在old区域。如果1秒之后这个数据页不再被访问了，就不会被移动到LRU链表头部，这样对Buffer Pool的命中率影响就不大。</p><p>但是，如果一个使用BNL算法的join语句，多次扫描一个冷表，而且这个语句执行时间超过1秒，就会在再次扫描冷表的时候，把冷表的数据页移到LRU链表头部。</p><p>这种情况对应的，是冷表的数据量小于整个Buffer Pool的3/8，能够完全放入old区域的情况。</p><p>如果这个冷表很大，就会出现另外一种情况：业务正常访问的数据页，没有机会进入young区域。</p><p>由于优化机制的存在，一个正常访问的数据页，要进入young区域，需要隔1秒后再次被访问到。但是，由于我们的join语句在循环读磁盘和淘汰内存页，进入old区域的数据页，很可能在1秒之内就被淘汰了。这样，就会导致这个MySQL实例的Buffer Pool在这段时间内，young区域的数据页没有被合理地淘汰。</p><p>也就是说，这两种情况都会影响Buffer Pool的正常运作。</p><p><strong>大表join操作虽然对IO有影响，但是在语句执行结束后，对IO的影响也就结束了。但是，对Buffer Pool的影响就是持续性的，需要依靠后续的查询请求慢慢恢复内存命中率。</strong></p><p>为了减少这种影响，你可以考虑增大join_buffer_size的值，减少对被驱动表的扫描次数。</p><p>也就是说，BNL算法对系统的影响主要包括三个方面：</p><ol>
<li>
<p>可能会多次扫描被驱动表，占用磁盘IO资源；</p>
</li>
<li>
<p>判断join条件需要执行M*N次对比（M、N分别是两张表的行数），如果是大表就会占用非常多的CPU资源；</p>
</li>
<li>
<p>可能会导致Buffer Pool的热数据被淘汰，影响内存命中率。</p>
</li>
</ol><p>我们执行语句之前，需要通过理论分析和查看explain结果的方式，确认是否要使用BNL算法。如果确认优化器会使用BNL算法，就需要做优化。优化的常见做法是，给被驱动表的join字段加上索引，把BNL算法转成BKA算法。</p><p>接下来，我们就具体看看，这个优化怎么做？</p><h1>BNL转BKA</h1><p>一些情况下，我们可以直接在被驱动表上建索引，这时就可以直接转成BKA算法了。</p><p>但是，有时候你确实会碰到一些不适合在被驱动表上建索引的情况。比如下面这个语句：</p><pre><code>select * from t1 join t2 on (t1.b=t2.b) where t2.b&gt;=1 and t2.b&lt;=2000;
</code></pre><p>我们在文章开始的时候，在表t2中插入了100万行数据，但是经过where条件过滤后，需要参与join的只有2000行数据。如果这条语句同时是一个低频的SQL语句，那么再为这个语句在表t2的字段b上创建一个索引就很浪费了。</p><p>但是，如果使用BNL算法来join的话，这个语句的执行流程是这样的：</p><ol>
<li>
<p>把表t1的所有字段取出来，存入join_buffer中。这个表只有1000行，join_buffer_size默认值是256k，可以完全存入。</p>
</li>
<li>
<p>扫描表t2，取出每一行数据跟join_buffer中的数据进行对比，</p>
<ul>
<li>如果不满足t1.b=t2.b，则跳过；</li>
<li>如果满足t1.b=t2.b, 再判断其他条件，也就是是否满足t2.b处于[1,2000]的条件，如果是，就作为结果集的一部分返回，否则跳过。</li>
</ul>
</li>
</ol><p>我在上一篇文章中说过，对于表t2的每一行，判断join是否满足的时候，都需要遍历join_buffer中的所有行。因此判断等值条件的次数是1000*100万=10亿次，这个判断的工作量很大。</p><p><img src="https://static001.geekbang.org/resource/image/92/60/92fbdbfc35da3040396401250cb33f60.png" alt=""></p><center><span class="reference">图6 explain结果</span></center><p><img src="https://static001.geekbang.org/resource/image/d8/9c/d862bc3e88305688df2c354a4b26809c.png" alt=""></p><center><span class="reference">图7 语句执行时间</span></center><p>可以看到，explain结果里Extra字段显示使用了BNL算法。在我的测试环境里，这条语句需要执行1分11秒。</p><p>在表t2的字段b上创建索引会浪费资源，但是不创建索引的话这个语句的等值条件要判断10亿次，想想也是浪费。那么，有没有两全其美的办法呢？</p><p>这时候，我们可以考虑使用临时表。使用临时表的大致思路是：</p><ol>
<li>
<p>把表t2中满足条件的数据放在临时表tmp_t中；</p>
</li>
<li>
<p>为了让join使用BKA算法，给临时表tmp_t的字段b加上索引；</p>
</li>
<li>
<p>让表t1和tmp_t做join操作。</p>
</li>
</ol><p>此时，对应的SQL语句的写法如下：</p><pre><code>create temporary table temp_t(id int primary key, a int, b int, index(b))engine=innodb;
insert into temp_t select * from t2 where b&gt;=1 and b&lt;=2000;
select * from t1 join temp_t on (t1.b=temp_t.b);
</code></pre><p>图8就是这个语句序列的执行效果。</p><p><img src="https://static001.geekbang.org/resource/image/a8/c7/a80cdffe8173fa0fd8969ed976ac6ac7.png" alt=""></p><center> 图8 使用临时表的执行效果</center><p>可以看到，整个过程3个语句执行时间的总和还不到1秒，相比于前面的1分11秒，性能得到了大幅提升。接下来，我们一起看一下这个过程的消耗：</p><ol>
<li>
<p>执行insert语句构造temp_t表并插入数据的过程中，对表t2做了全表扫描，这里扫描行数是100万。</p>
</li>
<li>
<p>之后的join语句，扫描表t1，这里的扫描行数是1000；join比较过程中，做了1000次带索引的查询。相比于优化前的join语句需要做10亿次条件判断来说，这个优化效果还是很明显的。</p>
</li>
</ol><p>总体来看，不论是在原表上加索引，还是用有索引的临时表，我们的思路都是让join语句能够用上被驱动表上的索引，来触发BKA算法，提升查询性能。</p><h1>扩展-hash join</h1><p>看到这里你可能发现了，其实上面计算10亿次那个操作，看上去有点儿傻。如果join_buffer里面维护的不是一个无序数组，而是一个哈希表的话，那么就不是10亿次判断，而是100万次hash查找。这样的话，整条语句的执行速度就快多了吧？</p><p>确实如此。</p><p>这，也正是MySQL的优化器和执行器一直被诟病的一个原因：不支持哈希join。并且，MySQL官方的roadmap，也是迟迟没有把这个优化排上议程。</p><p>实际上，这个优化思路，我们可以自己实现在业务端。实现流程大致如下：</p><ol>
<li>
<p><code>select * from t1;</code>取得表t1的全部1000行数据，在业务端存入一个hash结构，比如C++里的set、PHP的数组这样的数据结构。</p>
</li>
<li>
<p><code>select * from t2 where b&gt;=1 and b&lt;=2000;</code> 获取表t2中满足条件的2000行数据。</p>
</li>
<li>
<p>把这2000行数据，一行一行地取到业务端，到hash结构的数据表中寻找匹配的数据。满足匹配的条件的这行数据，就作为结果集的一行。</p>
</li>
</ol><p>理论上，这个过程会比临时表方案的执行速度还要快一些。如果你感兴趣的话，可以自己验证一下。</p><h1>小结</h1><p>今天，我和你分享了Index Nested-Loop Join（NLJ）和Block Nested-Loop Join（BNL）的优化方法。</p><p>在这些优化方法中：</p><ol>
<li>
<p>BKA优化是MySQL已经内置支持的，建议你默认使用；</p>
</li>
<li>
<p>BNL算法效率低，建议你都尽量转成BKA算法。优化的方向就是给被驱动表的关联字段加上索引；</p>
</li>
<li>
<p>基于临时表的改进方案，对于能够提前过滤出小数据的join语句来说，效果还是很好的；</p>
</li>
<li>
<p>MySQL目前的版本还不支持hash join，但你可以配合应用端自己模拟出来，理论上效果要好于临时表的方案。</p>
</li>
</ol><p>最后，我给你留下一道思考题吧。</p><p>我们在讲join语句的这两篇文章中，都只涉及到了两个表的join。那么，现在有一个三个表join的需求，假设这三个表的表结构如下：</p><pre><code>CREATE TABLE `t1` (
 `id` int(11) NOT NULL,
 `a` int(11) DEFAULT NULL,
 `b` int(11) DEFAULT NULL,
 `c` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

create table t2 like t1;
create table t3 like t2;
insert into ... //初始化三张表的数据
</code></pre><p>语句的需求实现如下的join逻辑：</p><pre><code>select * from t1 join t2 on(t1.a=t2.a) join t3 on (t2.b=t3.b) where t1.c&gt;=X and t2.c&gt;=Y and t3.c&gt;=Z;
</code></pre><p>现在为了得到最快的执行速度，如果让你来设计表t1、t2、t3上的索引，来支持这个join语句，你会加哪些索引呢？</p><p>同时，如果我希望你用straight_join来重写这个语句，配合你创建的索引，你就需要安排连接顺序，你主要考虑的因素是什么呢？</p><p>你可以把你的方案和分析写在留言区，我会在下一篇文章的末尾和你讨论这个问题。感谢你的收听，也欢迎你把这篇文章分享给更多的朋友一起阅读。</p><h1>上期问题时间</h1><p>我在上篇文章最后留给你的问题，已经在本篇文章中解答了。</p><p>这里我再根据评论区留言的情况，简单总结下。根据数据量的大小，有这么两种情况：</p><ul>
<li>@长杰 和 @老杨同志 提到了数据量小于old区域内存的情况；</li>
<li>@Zzz 同学，很认真地看了其他同学的评论，并且提了一个很深的问题。对被驱动表数据量大于Buffer Pool的场景，做了很细致的推演和分析。</li>
</ul><p>给这些同学点赞，非常好的思考和讨论。</p><p></p>
<style>
    ul {
      list-style: none;
      display: block;
      list-style-type: disc;
      margin-block-start: 1em;
      margin-block-end: 1em;
      margin-inline-start: 0px;
      margin-inline-end: 0px;
      padding-inline-start: 40px;
    }
    li {
      display: list-item;
      text-align: -webkit-match-parent;
    }
    ._2sjJGcOH_0 {
      list-style-position: inside;
      width: 100%;
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      margin-top: 26px;
      border-bottom: 1px solid rgba(233,233,233,0.6);
    }
    ._2sjJGcOH_0 ._3FLYR4bF_0 {
      width: 34px;
      height: 34px;
      -ms-flex-negative: 0;
      flex-shrink: 0;
      border-radius: 50%;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 {
      margin-left: 0.5rem;
      -webkit-box-flex: 1;
      -ms-flex-positive: 1;
      flex-grow: 1;
      padding-bottom: 20px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2zFoi7sd_0 {
      font-size: 16px;
      color: #3d464d;
      font-weight: 500;
      -webkit-font-smoothing: antialiased;
      line-height: 34px;
    }
    ._2sjJGcOH_0 ._36ChpWj4_0 ._2_QraFYR_0 {
      margin-top: 12px;
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-all;
      line-height: 24px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 {
      margin-top: 18px;
      border-radius: 4px;
      background-color: #f6f7fb;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._10o3OAxT_0 ._3KxQPN3V_0 {
      color: #505050;
      -webkit-font-smoothing: antialiased;
      font-size: 14px;
      font-weight: 400;
      white-space: normal;
      word-break: break-word;
      padding: 20px 20px 20px 24px;
    }
    ._2sjJGcOH_0 ._3klNVc4Z_0 {
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-orient: horizontal;
      -webkit-box-direction: normal;
      -ms-flex-direction: row;
      flex-direction: row;
      -webkit-box-pack: justify;
      -ms-flex-pack: justify;
      justify-content: space-between;
      -webkit-box-align: center;
      -ms-flex-align: center;
      align-items: center;
      margin-top: 15px;
    }
    ._2sjJGcOH_0 ._3Hkula0k_0 {
      color: #b2b2b2;
      font-size: 14px;
    }
</style><ul><li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/d4/fd/43802282.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>IceGeek17</span>
  </div>
  <div class="_2_QraFYR_0">节后补课，有几个问题：<br><br>问题一：<br>对于BKA算法的流程理解，用文中的例子，先把t1表（小表）中查询需要的字段放入join_buffer, 然后把join_buffer里的字段值批量传给t2表，先根据索引a查到id，然后得到一批主键id，再根据主键id排序，然后再根据排完序的id去主键索引查数据（这里用到MRR）<br>理解是否正确？<br>这里对于主键id排序是在哪里做的，是在join_buffer里，还是另外再开辟一块临时内存？如果在join_buffer里，那join_buffer里的每行内容是不是：t2.id + t1查询必须的字段，并且join_buffer里是根据id排序的？<br><br>问题二：<br>虽然MySQL官方没有支持hash join，但是之前看到文章说，MariaDB已经支持hash join，能不能后续在答疑文章中简单总结下mariaDB支持的join算法<br><br>问题三：<br>在实际项目中，一个比较困惑的问题，看到过这样的类似写法：<br>select xxx from t1 join t2 on t1.id = t2.id  for update （目的是获取几个表上最新的数据，并且加上锁，防止数据被更新）<br>这里有几个问题：<br>1) 像这样 join + for update，表上的加锁规则是怎么样的？是不是在需要join的两个表上根据具体的查询执行过程都加上锁？<br>2）像这样 join + for update 的用法是否合理？碰到这样的场景，应该怎么去做？<br><br>问题四：<br>看过阿里输出的开发手册里，强调 “最多不超过三表join”，实际项目中，给我感觉很难做到所有业务都不超过三表join，那这里的问题就是，有什么相关的经验方法，可以尽量降低参与join的数据表？<br>比如，在数据表里添加冗余字段，可以降低参与join的数据表数量，还有什么其他好的方法？<br><br><br></div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-02-18 17:02:47</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/03/f7/3a493bec.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>老杨同志</span>
  </div>
  <div class="_2_QraFYR_0">我准备给<br>t1增加索引c<br>t2增加组合索引b,c<br>t3增加组合索引b,c<br>select * from t1 straight_join t2 on(t1.a=t2.a)  straight_join  t3 on (t2.b=t3.b) where t1.c&gt;=X and t2.c&gt;=Y and t3.c&gt;=Z;<br><br>另外我还有个问题，开篇提到的这句sql select * from t1 where a&gt;=1 and a&lt;=100;<br>a是索引列，如果这句索引有order by a，不使用MRR 优化，查询出来就是按a排序的，使用了mrr优化，是不是要额外排序<br><br></div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 对，好问题，用了order by就不用MRR了</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-02-01 15:36:47</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/b8/36/542c96bf.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Mr.Strive.Z.H.L</span>
  </div>
  <div class="_2_QraFYR_0">老师您好，新年快乐~~<br><br>关于三表join有一个疑惑点需要确认：<br><br>老师您在评论中说到，三表join不会是前两个表join后得到结果集，再和第三张表join。<br>针对这句话，我的理解是：<br>假设我们不考虑BKA，就按照一行行数据来判断的话，流程应该如下（我会将server端和innodb端分的很清楚）：<br>表是t1 ,t2 ,t3。  t1 straight_join t2 straight_join t3，这样的join顺序。<br>1. 调用innodb接口，从t1中取一行数据，数据返回到server端。<br>2. 调用innodb接口，从t2中取满足条件的数据，数据返回到server端。<br>3. 调用innodb接口，从t3中取满足条件的数据，数据返回到server端。<br>上面三步之后，驱动表 t1的一条数据就处理完了，接下来重复上述过程。<br>（如果采用BKA进行优化，可以理解为不是一行行数据的提取，而是一个范围内数据的提取）。<br><br>按照我上面的描述，确实没有前两表先join得结果集，然后再join第三张表的过程。<br>不知道我上面的描述的流程对不对？（我个人觉得，将innodb的处理和server端的处理分隔清晰，对于sql语句的理解，会透彻很多）</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 新年快乐，分析得很好。<br><br>可以再补充一句，会更好理解你说的这个过程 ：<br>  如果采用BKA进行优化,每多一个join，就多一个join_buffer</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-02-02 13:18:22</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/d1/7c/4639f22c.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>郭健</span>
  </div>
  <div class="_2_QraFYR_0">老师，有几个问题还需要请教一下:<br>1.上一章t1表100条数据，t21000条数据，mysql会每次都会准确的找出哪张表是合理的驱动表吗？还是需要人为的添加straight_join。<br>2.像left join这种，左边一定是驱动表吧？以左边为标准查看右边有符合的条件，拼成一条数据，看到你给其他同学的评论说可能不是，这有些疑惑。<br>3.在做join的时候，有些条件是可以放在on中也可以放在where中，比如(t1.yn=1 和t2.yn=1)这种简单判断是否删除的。最主要的是，需要根据两个条件才能join的(productCode和custCode),需要两个都在on里，还是一个在on中，一个在where中更好呢？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 1. 正常是会自己找到合理的，但是用前explain是好习惯哈<br>2. 这个问题的展开我放到答疑文章中哈<br>3. 这也是好问题，需要分析是使用哪种算法，也放到答疑文章展开哈。<br><br>新年快乐~<br></p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-02-07 00:05:16</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/e9/29/629d9bb0.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>天王</span>
  </div>
  <div class="_2_QraFYR_0">join语句的优化，NLJ算法的优化，MRR优化器会在join_buffer进行主键的排序，然后去主键索引树上一个个的查找，因为按照主键顺序去主键索引树上查找，性能会比较高，MRR优化接近顺序读，性能会比较高。BKA算法是对NLJ算法的优化，一次取出一批数据的字段到join_buffer中，然后批量join，性能会比较好。BKA算法依赖于MRR，因为批量join找到被驱动表的非聚集索引字段通过MRR去查找行数据</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 👍</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-02-13 08:54:57</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/ed/1a/ce7f7d54.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>asdf100</span>
  </div>
  <div class="_2_QraFYR_0">最近遇到这个需求，in里面的值个数有5万左右，出现的情况很少但存在，这种情况怎么处理。？手动创建临时表再join？<br><br>另外in内的值用不用手动排序？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 不需要手动排序<br><br>不过5万个值太凶残了，语句太长不太好<br><br>这种就是手动创建内存临时表，建上hash索引，填入数据，然后join</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-02-01 11:00:21</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/14/ba/ac/b44b256a.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>纸片人</span>
  </div>
  <div class="_2_QraFYR_0">知识点总结<br>1、翻译嵌套查询过程<br>Join = 嵌套查询<br>示例：t1 join t2 on t1.a=t2.a where t1.id&gt;=X and t2.id&gt;=Y<br>已知：驱动表t1的规模是N，被驱动表t2的规模是M<br>伪代码：<br>SNLJ: 扫描次数NxM, 计算次数NxM<br>for r1 in t1 &#47;* read from ~~disk~~ InnoDB(storage engine) *&#47;<br>  for r2 in t2<br>    if r1.a=r2.a<br>      if r1.id&gt;=X and r2.id&gt;=Y<br>        add r1+r2 into result_set<br>  end<br>end<br><br><br>BNLJ: 扫描次数N+KxM，计算次数NxM，K = Nxrow_size&#47;join_buffer_size+1<br>for r1 in t1<br>  add r1 into join_buffer<br>end<br>for r2 in t2<br>  for r1 in join_buffer &#47;* read from ~~memory~~ join_buffer which is controlled by Server *&#47;<br>    if r1.a=r2.a<br>      if r1.id&gt;=X and r2.id&gt;=Y<br>        add r1+r2 into result_set<br>  end<br>end<br><br><br>INLJ: 扫描次数&lt;N+N，计算次数N + Nx2xlnM&#47;ln2<br>for r1 in t1<br>  locate r1.a on t2 with index a  &#47;* search B+Tree *&#47;<br>  if r1.id&gt;=X and r2.id&gt;=Y<br>    add r1+r2 into result_set<br>end<br><br><br><br>2、Join优化思路<br>MRR优化思想：回表前，按主键排序，执行读取操作，以保证顺序读。<br>BKA算法：先按BNLJ的思想批量扫描驱动表数据，再将之按被驱动表上的索引排序，取值。<br>示例：t1 join t2 on t1.a=t2.b where t1.id&gt;=X and t2.id&gt;=Y<br>伪代码：<br>for r1 in t1<br>  add r1 into join_buffer<br>end <br>sort join_buffer by r1.a<br>for r1 in sort_buffer<br>  locate r1.a on t2 with index b  &#47;* search B+Tree *&#47;<br>  if r1.id&gt;=X and r2.id&gt;=Y<br>    add r1+r2 into result_set<br>end<br><br><br>3、BNLJ转BKA<br>方案一：在被驱动表的join字段上添加索引，相当于BNLJ先转INLJ再转BKA。<br>方案二：不适宜添加索引的情况（查询语句使用频率较低），引入临时表。具体操作步骤如下：<br>   a. 先根据where过滤被驱动表t2，并将结果存入临时表tmp_t；<br>   b. 在临时表上为join字段b添加索引；<br>   c. 让驱动表t1连接临时表tmp_t。<br>（注意，由于步骤b中需要为临时表创建索引，所以此方案当且仅当tmp_t规模远小于t2时才划算！）<br><br><br>4、扩展hash-join<br>可视为BNLJ进阶，将join_buffer变成Hash表，处理流程如下：<br><br>hash-join:<br>for r1 in t1<br>  add &lt;key:r1.a,value:r1&gt; into join_buffer<br>end<br>for r2.a in t2 <br>  locate r2.a in t1 with hash index &#47;* read from join_buffer *&#47;<br>  if r1.id&gt;=X and r2.id&gt;=Y<br>    add r1+r2 into result_set<br>end<br><br><br>分析：<br>   a. 驱动表在内环，以降低内存占用率。<br>   b. 如果t1的尺寸大于join_buffer，那么我们就不得不多次全表扫描t2了。因为过滤条件的逻辑运算符号是and，所以还有优化的余地，可将驱动表的过滤提前，来降低t1的大小。<br>for r1 in t1<br>  if r1.id&gt;=X<br>    add &lt;key:r1.a,value:r1&gt; into join_buffer<br>end<br>for r2.a in t2 <br>  locate r2.a in t1 with hash index &#47;* read from join_buffer *&#47;<br>  if r2.id&gt;=Y<br>    add r1+r2 into result_set<br>end<br></div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-06-11 04:41:07</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/e9/0b/1171ac71.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>WL</span>
  </div>
  <div class="_2_QraFYR_0">请教老师两个问题:<br>1. 通过主键索引找到的数据会会不会先在内存中查询, 如果没有再去磁盘查询?<br>2. 为什么在通过主键索引查询数据时, 符合条件的数据以单条数据的方式读到内存中而不是以一整个数据页的方式读到内存中? </div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 1. 通过普通索引也会，InnoDB的访问模式都是先内存，不在内存中，才到磁盘找；<br>2. 是以数据页的方式读到内存的，然后在从内存的这个数据页（默认16k）里面找到数据。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-02-11 15:00:06</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/11/11/18/8cee35f9.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>HuaMax</span>
  </div>
  <div class="_2_QraFYR_0">前提假设：t1.c&gt;=X可以让t1成为小表。同时打开BKA和MRR。<br>1、t1表加（c,a)索引。理由：A、t1.c&gt;=X可以使用索引；B、加上a的联合索引，join buffer里放入的是索引（c,a）而不是去主键表取整行，用于与表t2的t1.a = t2.a的join查询，不过返回SELECT * 最终还是需要回表。<br>2、t2表加(a,b,c)索引。理由：A、加上a避免与t1表join查询的BNL；B、理由同【1-B】；C、加上c不用回表判断t2.c&gt;=Y的筛选条件<br>3、t3表加（b,c）索引。理由：A、避免与t2表join查询的BNL;C、理由同【2-C】<br><br>问题：<br>1、【1-B】和【2-B】由于select *要返回所有列数据，不敢肯定join buffer里是回表的整行数据还是索引（c,a)的数据，需要老师解答一下；不过值得警惕的是，返回的数据列对sql的执行策略有非常大的影响。<br>2、在有join查询时，被驱动表是先做join连接查询，还是先筛选数据再从筛选后的临时表做join连接？这将影响上述的理由【2-C】和【3-C】<br><br>使用straight_join强制指定驱动表，我会改写成这样:select * from t2 STRAIGHT_JOIN t1 on(t1.a=t2.a) STRAIGHT_JOIN t3 on (t2.b=t3.b)  where t1.c&gt;=X and t2.c&gt;=Y and t3.c&gt;=Z;<br>考虑因素包括：<br>1、驱动表使用过滤条件筛选后的数据量，使其成为小表，上面的改写也是基于t2是小表<br>2、因为t2是跟t1,t3都有关联查询的，这样的话我猜测对t1,t3的查询是不是可以并行执行，而如果使用t1,t3作为主表的话，是否会先跟t2生成中间表，是个串行的过程？<br>3、需要给t1加（a,c)索引，给t2加（c,a,b）索引。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 👍 很深入的思考哈<br>1. select * ，所以放整行；你说得对，select * 不是好习惯；<br>2. 第一次join后就筛选；第二次join再筛选；<br>新春快乐~</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-02-02 11:42:35</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eqJobg767PUeqrqQQ4B6YvMatj2SRyOicKZZ4gWTf30dMketiaj58Gc3RFTmckGxAXlL9ERSxGovq9g/132"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>涛哥哥</span>
  </div>
  <div class="_2_QraFYR_0">老师，对于现在的固态硬盘，这样类似顺序读写的数据库优化，不就不起作用了啊？</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 固态硬盘的顺序写还是比随机写快的</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-03-20 08:46:54</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/18/07/c0a73a52.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>憶海拾貝</span>
  </div>
  <div class="_2_QraFYR_0">节后开工宜补课.<br><br>按照文中说明的MRR设计思路, 是否可以反推出: 被驱动表使用非递增主键(比如UUID作为主键),就没有必要开启MRR?</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 如果是非随机的主键，确实没必要了😅<br><br>优化第一步还是应该把主键处理一下<br></p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-02-13 11:43:27</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/10/62/a5/43aa0c27.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>TKbook</span>
  </div>
  <div class="_2_QraFYR_0">BNL 算法效率低，建议你都尽量转成 BKA 算法。优化的方向就是给驱动表的关联字段加上索引；<br>老师最后总结的时候，这句话后面那句，应该是给被驱动表的关联字段加上索引吧。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 对的，👍细致<br><br>已经发起勘误，谢谢你哦，新年快乐</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-02-01 15:00:25</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/b8/36/542c96bf.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>Mr.Strive.Z.H.L</span>
  </div>
  <div class="_2_QraFYR_0">老师你好，今天在回顾这篇文章做总结的时候，突然有一个疑惑：<br><br>我们假设t2的b上面有索引，该语句是左连接<br><br>select * from t1  left join t2 on (t1.b=t2.b) where t2.b&gt;=1 and t2.b&lt;=2000;<br><br>和<br><br>select * from t1  left join t2 on (t1.b=t2.b) and t2.b&gt;=1 and t2.b&lt;=2000;<br><br>到底在内部执行流程上到底有什么区别？？<br>因为实际工作中左连接用得挺多的。<br>（这篇文章都是直连，所以使用on和where最后的结果都一样，但是左连接就不是了）<br></div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 你这两个语句是一样的。。是不是第二个语句多了left？<br><br>left join因为语义上要求所有左边表的数据行都必须存在结果里面，所以执行流程不太一样，我在答疑文章中说哈</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-02-13 11:21:49</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/ee/23/b92b0811.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>人间</span>
  </div>
  <div class="_2_QraFYR_0">刚刚凌乱了的那个问题，经explain验证，explain SELECT a.* FROM sys_xxtx a JOIN baq_ryxx r ON a.ryid = r.ID WHERE a.dwbh = &#39;7E0A13A14101D0A8E0430A0F23BCD0A8&#39; ORDER BY txsj DESC LIMIT 0,20;<br>使用的索引是txsj ；<br>explain SELECT a.* FROM sys_xxtx a JOIN baq_ryxx r ON a.ryid = r.ID WHERE a.dwbh = &#39;7E0A13A14101D0A8E0430A0F23BCD0A8&#39; ORDER BY txsj DESC LIMIT 5000,20;使用的索引是dwbh ；<br>然后回忆起了第10张：MySQL为什么有时候会选错索引？<br>但是从扫描行数、是否使用排序等来看在 LIMIT 5000,20时候也应该优选txsj ?可是这个时候选择的索引是dwbh, 查询时间也大大缩短</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 嗯，这个跟我们第十篇那个例子挺像的<br><br>我们把limit 1 改成limit 100的时候，MySQL认为，要扫描到“100行那么多”，<br>你这里是limit 5000，200， 这个5000会让优化器认为，选txsj会要扫“很多行，可能很久”<br><br>这个确实是优化器还不够完善的地方，有时候不得不用force index~</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-02-01 16:30:44</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/12/0c/48/ba59d28d.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>poppy</span>
  </div>
  <div class="_2_QraFYR_0">select * from t1 join t2 on(t1.a=t2.a) join t3 on (t2.b=t3.b) where t1.c&gt;=X and t2.c&gt;=Y and t3.c&gt;=Z;<br>老师，我的理解是真正做join的三张表的大小实际上是t1.c&gt;=X、t2.c&gt;=Y、t3.c&gt;=Z对应满足条件的行数，为了方便快速定位到满足条件的数据，t1、t2和t3的c字段最好都建索引。对于join操作，按道理mysql应该会优先选择join之后数量比较少的两张表先来进行join操作，例如满足t1.a=t2.a的行数小于满足t2.b=t3.b的行数，那么就会优先将t1和t2进行join，选择t1.c&gt;=X、t2.c&gt;=Y中行数少的表作为驱动表，另外一张作为被驱动表，在被驱动表的a的字段上建立索引，这样就完成了t1和t2的join操作并把结果放入join_buffer准备与t3进行join操作，则在作为被驱动表的t3的b字段上建立索引。不知道举的这个例子分析得是否正确，主要是这里不知道t1、t2、t3三张表的数据量，以及满足t1.c&gt;=X ，t2.c&gt;=Y ，t3.c&gt;=Z的数据量，还有各个字段的区分度如何，是否适合建立索引等。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 嗯 这个问题就是留给大家自己设定条件然后分析的，分析得不错哦</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-02-01 15:24:30</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/ee/23/b92b0811.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>人间</span>
  </div>
  <div class="_2_QraFYR_0">order by cjsj desc limit 0,20 explain  Extra只是显示 Using where ，执行时间 7秒钟<br>order by cjsj desc limit 5000,20 explain  Extra只是显示 Using index condition; Using where; Using filesort,  执行时间 0.1 秒<br>有些许的凌乱了@^^@</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 这正常的，一种可能是这样的： <br>   Using where 就是顺序扫，但是这个上要扫很久才能扫到满足条件的20个记录；<br>   虽然有filesort，但是如果参与排序的行数少，可能速度就更快，而且limit 有堆排序优化哦</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-02-01 13:32:30</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/dd/ac/f40bbd15.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>dzkk</span>
  </div>
  <div class="_2_QraFYR_0">老师，记得我之前看mysql的join是和版本有关系的，另外NLJ是一个统称，被分为了SNLJ(Simple Nested-Loop Join，5.5版本之前采用的，当被驱动表上没有索引的时候使用，该方法比较粗暴，所以后来通过BNLJ进行了优化)、INLJ(Index Nested-Loop Join，被驱动表上有索引)、BNLJ(Block Nested-Loop Join，被驱动表上没有索引)，另外了解到mariadb是支持了hash join的Block Nested Loop Hash (BNLH) join，没有使用过，不知道效果怎么样。不知道我了解的信息对不对。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 你了解得比较全面哈<br><br>不过我怕在文章中写这么多概念，会看得晕。<br><br>实际上现在 Simple Nested-Loop Join 已经不会用了（太慢），有使用的就是 Index Nested-Loop Join 和 BKA优化哈。<br><br>MariaDB在优化器上做了很多工作，之前的文章本来也想介绍，后来发现得先把官方版本的说明白，然后我们可以在评论区扩展讨论。<br><br>BNLH 在MariaDB 5.3就引入了，流程跟我们“扩展-hash join”这段类似，对于等值join的效果还是很好的。</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-02-01 09:56:09</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src=""
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>bluefantasy3</span>
  </div>
  <div class="_2_QraFYR_0">请教老师一个问题：innodb的Buffer Pool的内存是innodb自己管理还是使用OS的page cache? 我理解应该是innodb自己管理。我在另一个课程里看到如果频繁地把OS的&#47;proc&#47;sys&#47;vm&#47;drop_caches 改成 1会影响MySQL的性能，如果buffer pool是MySQL自己管理，应该不受这个参数影响呀？请解答。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 1. 是MySQL 自己管理的<br>2. 一般只有数据文件是o_direct的，redo log 和 binlog 都是有用到文件系统的page cache, 因此多少有影响的<br><br>好问题👍🏿 </p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-02-02 17:05:33</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/0f/63/c6/d6ea3df3.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>林肯</span>
  </div>
  <div class="_2_QraFYR_0">可喜的是：MySQL8.0已经支持hash join了</div>
  <div class="_10o3OAxT_0">
    
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2020-08-21 18:19:54</div>
  </div>
</div>
</div>
</li>
<li>
<div class="_2sjJGcOH_0"><img src="https://static001.geekbang.org/account/avatar/00/13/e0/d5/7485e51f.jpg"
  class="_3FLYR4bF_0">
<div class="_36ChpWj4_0">
  <div class="_2zFoi7sd_0"><span>~玲玲玲~子~哥~</span>
  </div>
  <div class="_2_QraFYR_0">老师好。<br>这节课评论我看好多人都问三表连接时的步骤，您指出和第三个表关联不是前两个表的结果集和第三张表关联。<br>那是第一张表和第二张表关联，然后第一张表在和第三张表关联，最后这两个结果集在关联？请纠正。</div>
  <div class="_10o3OAxT_0">
    <p class="_3KxQPN3V_0">作者回复: 不是，要看join算法。<br><br>比如如果都是BKA算法，那就是1关联2再马上关联3，得到结果，这样循环</p>
  </div>
  <div class="_3klNVc4Z_0">
    <div class="_3Hkula0k_0">2019-03-31 21:00:02</div>
  </div>
</div>
</div>
</li>
</ul>